
new_list = [11,22,33,44,55,66,77,88,99]
print(new_list[5::1])

list1 = [34,6,65,6,23,5,6,6,6,6,23]
#使用添加
new_list = []
for ele in list1:
    if ele != 6 :
        new_list.append(ele)
print(new_list)

#while + 索引移位
i = 0
while i <len(list1):
    if list1[i] == 6:
        list1.remove(6)
        i -=1
    i +=1
print(list1)

#拷贝
for num in list1.copy():
    if num == 6:
        list1.remove(6)
print(list1)

#创建空字典
dic = {}
print(type(dic))
# 输出结果：<class 'dict'>

#直接赋值创建字典
dic1 = {'name': 'Jack', 'age': 18, 'height': 180}
print(dic1)
# 输出结果：{'name': 'Jack', 'age': 18, 'height': 180}

#dict() 创建字典
dic2 = dict(name='Jack', age=18, height=180)
print(dic2)
# 输出结果：{'name': 'Jack', 'age': 18, 'height': 180}

#通过关键字dict和二元组列表创建
lis = [('name', 'Jack'), ('age', 18), ('height', 180)]
dic3 = dict(lis)
print(dic3)
# 输出结果：{'name': 'Jack', 'age': 18, 'height': 180}

#通过关键字dict和zip创建
dic4 = dict(zip('abcrt', [1, 2, 3]))
print(dic4)
# 输出结果：{'a': 1, 'b': 2, 'c': 3}

dic5 = dict(zip(["a","b","c"], [1, 2, 3]))
print(dic5)

#通过字典推导式创建
dic6 = {i: i **2 for i in range(1,5)}
print(dic6)

#通过dict.fromkeys()创建  --- 注意：通常用来初始化字典, 设置value的默认值
dic7 = dic.fromkeys(range(4),'x')
print(dic7)
# 输出结果：{0: 'x', 1: 'x', 2: 'x', 3: 'x'}

dic7 = {('name,age'):'王贤松，25'}
print(dic7)


info_dict = {'name':'王贤松','age':'25','addr':'西安'}

#遍历字典，得到的是键
for i in info_dict:
    print(i)

#得到键值对
for i in info_dict:
    print(i,info_dict[i])

#字典的键
print(info_dict.keys())
for key in info_dict.keys():
    print(key)

#字典的值
print(info_dict.values())
for value in info_dict.values():
    print(value)
'''

'''
info_dict = {'name':'王贤松','age':'25','addr':'西安'}
#print(info_dict.items())
for i in info_dict.items():
    print(i)

#同时获取键值对
for k,v in info_dict.items():
    print(k,v)
'''
'''
new_list = [34,6,67,8,8,23,23,4,45,57,68,23,23]
new_dict = {}
for i in new_list:
    new_dict[str(i)] = new_list.count(i)
print(new_dict)
'''
'''
info_dict = {'name':'王贤松','age':'25','addr':'西安'}
new_dict = {}
#使用items()
for k,v in info_dict.items():
    new_dict[v] = k
print(new_dict)
#使用zip
dict9 = dict(zip(info_dict.values(),info_dict.keys()))
print(dict9)

dict10 = {v:k for k,v in info_dict.items()}
print(dict10)

dict11 = {i:i**2 for i in range(1,10,2)}
print(dict11)

dict11 = {i:i**2 for i in range(1,10,1) if i %2 ==1}
print(dict11)

list3 = {i:j for i in 'abc' for j in '123'}
print(list3)

#函数
#? 函数的本质就是对某些特殊功能的封装
#* 优点 (1)简化代码结构，提高应用的模块性 （2）提高了代码的复用性  （3）提高了代码维护性 
#* 函数参数 （1）必需参数 （2）默认参数 （3）关键字参数 （4）不定长参数
#! 如果形参有多个，可以必需参数和默认参数混合使用，必需参数居首位，默认参数靠后书写
#? 默认参数出现的意义就是减少函数的调用
#? 关键字参数传参可以不用匹配顺序，但是关键字需要和形参的变量名保持一致；同样的，关键字参数也要写在必需参数后面
#? 不定长参数: 可以接受多个参数,如果出现在形参当中，可以结合关键字参数完成传参，形参中只能出现一次

def func1(name,age):
    print("姓名:%s,年龄：%d"%(name,age))
func1(age =10,name="张三")

#! "*" 会被当做元组进行处理
def check1(*num):
    print(num)
check1("abc",45,56,67,7)
#('abc', 45, 56, 67, 7)

#! "**" 会被当做字典进行处理
def check2(**num):
    print(num)
check2(abc = 34 ,y = 2,c = 67)
# {'abc': 34, 'y': 2, 'c': 67}

def check3(*a,**b):
    print(a,b)
check3(45,56,89,894,abc = 34 ,y = 2,c = 67)
#输出结果：(45, 56, 89, 894) {'abc': 34, 'y': 2, 'c': 67}

'''
#?! 值传递和引用传递的区别；
#? 值传递的时候，传递的是不可变的数据类型(int,float,string,tuple,bool),当形参发生改变的时候，对实参没有影响；
#? 引用传递的时候，传递的是数据类型(list,dict,set),当形参中的元素发生修改，则实参会随着修改
'''
#? 值传递
def func1(num):
    print(num)
    num = 100
    print(num)
temp = 45
func1(temp)
print(temp)


#! 面试题，定义和调用的区别
def func8():
    l = []
    for i in range(5):
        l.append(lambda x:i*x)
    return l
r = func8()
print(r)  #! 获得是列表
print(r[0]) #! 获取列表中的元素：匿名函数
print(r[0](3)) #! 调用函数，输出的结果是12

#* 函数进阶 -- 闭包
#? 嵌套定义的函数如何调用
#  -- 方式1
def func9():
    print("111")
    num = 45
    def func10():
        print("222")
        print(num+5)
    func10()
    print(333)
func9()

# -- 方式2
def func10():
    print("111")
    num = 45
    def func11():
        print("222")
        print(num+5)
    return func11  #! 这个地方并没有调用函数，只是表示函数本身
func10()() #! 调用函数func11()

#? 函数返回值：表示当前函数的运算结果在哪里调用函数，返回值就返回到哪里，函数如果没有使用return，默认返回 None
#? return 是一个关键字，可以单独使用，作用：结束函数

#? 空函数
def test():
    pass

#? 主函数 java：main() 函数，整个程序的入口;
#? 但是在Python没有主函数的概念，但是为了代码的可读性和可复用性
#? 结合模块的使用：将函数的调用书写到 if __name__ == "__main__": 中

def check():
    print("~~~~~ok")

if __name__ == "__main__":
    check()

#? 函数封装 
#? 1,是否需要设置参数,当需求中有未知项参与运算【数量】
#? 2.是否需要设置返回值，查看需求运算完毕之后是否有结果
#! 函数中使用print(),就仅仅是一个代码调试工具，对于程序本身而言没有任何意义
#! 如果函数运算结束的结果需要在函数外面使用,则将该结果进行return
#! 后期实际开发中，建议使用return 代替print(),内部实现的过程中不要出现打印

#* 封装练习 : 判断一个年份是否是闰年
def  isleapyear(year):
    """
    功能：判断一个年份是否是闰年
    param: year 年份
    return: bool 闰年返回True,否则 False
    """
    #? 方式一
    # if (year%4 == 0 and year%100 !=0) or (year%400 == 0):
    #     return True
    # return False
    
    #? 方式二
    return (year%4 == 0 and year%100 !=0) or (year%400 == 0)
print(isleapyear(2020))

#? 匿名函数 不在使用def这种标准形式定义函数，而是使用lambda 表达式来创建函数
#? 这种函数没有函数名,所以叫匿名函数
#? 语法形式：lambda 形参列表:返回值
#? 本质是一个表达式，一行代码，实现一个简单的逻辑，也就是说只能封装简单的逻辑

#? example -- 
func = lambda :print("~~~~~")
print(func) #* 函数本身 <function <lambda> at 0x00000204644AF0D0>
func() #* 函数调用

(lambda :print("~~~~~"))() #? 函数调用

#? 匿名函数的返回值如何处理？
lam_1= lambda num1,num2:num1+num2
print((lam_1(10,5)))

lam_2 = lambda a,b: a**2 + b**2
print(lam_2(2,2))

lam_3 = lambda a,b=2: a**2 + b**2
print(lam_3(a=2,b=3))

lam_4 = lambda *a:sum(a)
print(lam_4(1,2,3,4,5))

#? 匿名函数中的条件判断
lam_5 = lambda num1,num2: True if num1==num2 else False
print(lam_5(1,3))














#* 闭包  如果两个函数嵌套定义，如果在内部函数中访问了外部函数中的变量，则会形成一个闭包
def outter(a):
    def inner(b):
        print(a,b)
        return 10
    return inner
outter(2)(3) 
print(outter(2)(3))

#! 闭包本质还是一个函数，只要遵循闭包的概念，可以设置默认参数，关键字参数，不定长参数和返回值
#! 闭包的应用场景，变量的作用域和装饰器

#* 变量的作用域
#* 作用域的分类
#? 变量定义在不同的位置，。访问的权限和位置也不同
num1 = 110 #? 全局作用域：global 可以在当前py文件中任意位置访问
def ouutter():
    num2 = 120   #? 函数作用域 enclosing 只能在外部函数中访问
    def inner():
        num3 = 113  #? 局部作用域 local 只能在内部函数被访问
        print("--1",num1,num2,num3)
    print("--2",num1,num2)
    return inner
ouutter()()
print("--3",num1)


#! UnboundLocalError: local variable 'a' referenced before assignment
a = 10
def test():
    a +=1
test()
print(a)

#! 修改以上代码
a = 10 
def test():
    global a
    a +=1
    print("--1",a)
test()
print("--2",a)


#? nonlocal: 不是局部，只能使用在函数的嵌套定义或者闭包中
def test():
    name = "xyz"
    def in_test():
        nonlocal name 
        name += "123"
        print("--1",name)
    print("--2",name)
    return in_test
test()()


#? 生成器-[重点掌握] 可迭代对象[面试]  迭代器[重点掌握]

#? 抛出问题
#? 列表：一次性将所有的元素全部定义处理啊，如果只需要访问其中的几个元素，则大量的内存内容会被浪费
    #? 比如生成一个列表.，其中有10万个元素，仅仅是需要访问前五个元素
#? 换种思路来说，需要使用前n个元素，则只有前N个元素会占用空间。
#? 在Python当中，将这种一边使用，一边计算的机制被称为生成器。[generator]

#? 生成器的定义方式：
'''
    <1> 列表推导式中的 [] 换成 () 
    <2> 函数结合yield 定义函数生成器
'''
list1 = [i**2 for i in range(6)]
print(list1,type(list1))
#? [0, 1, 4, 9, 16, 25] <class 'list'>

ge1 = (i**2 for i in range(6))
print(ge1,type(ge1))
#? <generator object <genexpr> at 0x00000282ACA5ED60> <class 'generator'>

#? 如果要访问生成器中的元素，需要使用next()
print(next(ge1)) #? 0
print(next(ge1)) #? 1
print(next(ge1)) #? 4
print(next(ge1)) #? 9
print(next(ge1)) #? 16
print(next(ge1)) #? 25
print(next(ge1)) #! 如果生成器中的元素全部获取完毕，继续获取，则会报错StopIteration
#? 也就是这里理解为，生成器是一个预生成的容器
#TODO 使用for循环遍历得到生成器中的所有元素
for ele in ge1:
    print(ele)

#TODO 使用list() 将生成器转化成一个列表
print(list(ge1))


#TODO return 表示的函数返回值
def test1():
    return 10
r1 = test1()
print(r1,type(r1))
#? 10 <class 'int'>

#TODO yiele 在函数内部出现yield关键字，则该函数表示一个函数生成器，那么yield关键字后面的数据将是生成器中的元素
def test2():
    yield 10
    yield 20
r2 = test2()
print(r2,type(r2))
#? <generator object test1 at 0x00000243202DED60> <class 'generator'>
print(next(r2))
#? 10
print(next(r2))


def test3():
    yield 10
    yield 20
    yield 30
r3 =test3()
for i in r3:
    print(i)


def test4(n):
    for ele in range(n):
        yield  ele**2
r4 = test4(10)
print(next(r4))
print(next(r4))
print(next(r4))

#TODO 函数生成器每调用一次，都表示生成了一个新的生成器


#? 迭代器和可迭代对象
#? 区别
'''
可迭代对象 Iterable 可以直接作用于for循环的对象[可以使用for循环遍历其中元素的对象]
    如：list,tuple,dict,set,string,range(),generator等。
迭代器：Iterator 可以直接作用于for循环，或者可以通过next() 获取元素的对象
    如：generator
'''
#? 联系
'''
    迭代器一定是可迭代对象，但是可迭代对象不一定是迭代器
    可以通过系统功能iter() 将不是迭代器的可迭代对象转化为迭代器
'''

import imp
import pstats
#? 举例一
from ast import operator
from collections import Iterable, Iterator
from ctypes.wintypes import WORD
from curses import ACS_GEQUAL
from encodings import utf_8
from hashlib import new
from mimetypes import init
from os import listdir, stat
from unicodedata import name

from numpy import isin
from pip import main

print(isinstance([3,4],Iterable))
print(isinstance((3,4),Iterable))
print(isinstance({3,4},Iterable))
print(isinstance({"name":"Jack"},Iterable))
print(isinstance("JackLove",Iterable))
print(isinstance(range(3),Iterable))
print(isinstance((i for i in range(3)),Iterable))

#? 举例二
from collections import Iterable, Iterator

print(isinstance([3,4],Iterator))
print(isinstance((3,4),Iterator))
print(isinstance({3,4},Iterator))
print(isinstance({"name":"Jack"},Iterator))
print(isinstance("JackLove",Iterator))
print(isinstance(range(3),Iterator))
print(isinstance((i for i in range(3)),Iterator))

#? 举例三
from collections import Iterable, Iterator

print(isinstance(iter([3,4]),Iterator))
print(isinstance(iter((3,4)),Iterator))
print(isinstance(iter({3,4}),Iterator))
print(isinstance(iter({"name":"Jack"}),Iterator))
print(isinstance(iter("JackLove"),Iterator))
print(isinstance(iter(range(3)),Iterator))
print(isinstance((i for i in range(3)),Iterator))

#TODO print(next(iter("JackLove")))


#? 函数的本质其实是一个变量，函数名其实就是一个变量名
#? 一个函数可以作为另一个函数的参数或者返回值，只需要传递函数名即可
def test(num1,num2):
    return  num1+num2
def fun2(f):
    return f
r2 = fun2(test)
print(r2)
r2(1,2)
r3 = r2(1,2)
print(r3)


#TODO高阶函数的学习

#? map() 函数使用
#? map(function,iterable) 返回值是个iterator【容器，迭代器】
#! function的形参个数必须和iterable的个数保持一致

#? 生成一个容器
#& 方式一
n_list = [i**2 for i in range(1,6)]
print(n_list)

#& 方式二
def func1(num):
    return num**2
n_map = map(func1,range(1,6))
print(n_map)
#print(list(n_map)) #? [1, 4, 9, 16, 25]
#? <map object at 0x0000017B01EAC1C0>
for ele in n_map:
    print(ele)
#? TypeError: func1() takes 0 positional arguments but 1 was given

#& 方式三：一般情况下，如果需要自定义的函数作为参数使用，逻辑比较简单，建议使用匿名函数
n_map = map(lambda x:x**2,range(1,6))
print(n_map)
print(list(n_map))


#& 多个容器
def func2(a,b):
    return a,b
n_map = map(func2,[1,2,3],[4,5,6])
print(list(n_map))
#? [(1, 4), (2, 5), (3, 6)]

n_map =map(lambda x,y:(x+y),[1,2,3,6],[4,5,6,7])
print(list(n_map))
#? [5, 7, 9, 13]


#? reduce() 函数使用 
#! reduce中的function 一定是两个参数，最终得到的结果是一个值
'''
reduce(func1,seq)
    func:函数
    seq:序列[容器]
功能：减少
工作原理：seq --> func -->value
'''
#& 举例分析求出1 - 100之间的和
#& 方式一
total = 0
for ele in range(1,101,1):
    total += i 
print(total)

#& 方式二
total = sum(range(1,101,1))
print(total)

#& 方式三
from functools import reduce


def func(n,m):
    return n+m
n_red = reduce(func,range(1,101))
print(n_red)

#& 方式四
from functools import reduce

n_red = reduce(lambda x,y:x+y,range(1,101))
print(n_red)

#? 练习一
from functools import reduce

n_red = reduce(lambda x,y:x*y,range(1,21,1))
print(n_red)

#? 练习二
from functools import reduce

n_red = reduce(lambda x,y:str(x)+str(y),[2,3,5,9])
print(int(n_red))

from functools import reduce

n_red = reduce(lambda x,y:x*10+y,[2,5,3,9])
print(n_red)

#? filter() -- 过滤 -- 返回值是一个迭代器
'''
filter(func,iterable):
    func:函数
    iterbale:可迭代对象[容器]
功能：将iterbale中的元素依次传递给func，根据func的返回值决定是否保留该元素
    如果func的返回值为True,则表示当前元素需要保留,如果为False，则表示过滤
'''

#& 过滤列表中的所有奇数

#& 方式一
n_list = [234,45,56,76,8,2,34,56,6,1,2,34,56,19,45]
list2 = []
for ele in n_list:
    if ele%2==0:
        list2.append(ele)
print(list2)

#& 方式二
n_list = [234,45,56,76,8,2,34,56,6,1,2,34,56,19,45]
list2 = [ele for ele in n_list if ele%2== 0]
print(list2)

#& 方式三
n_list = [234,45,56,76,8,2,34,56,6,1,2,34,56,19,45]
def func(num):
    '''
    if num%2==0:
        return True
    return  False
    '''
    return num%2==0
n_fil = filter(func,n_list)
print(n_fil)
print(list(n_fil))

#& 方式四 -- 匿名函数
n_list = [234,45,56,76,8,2,34,56,6,1,2,34,56,19,45]
n_fil = filter(lambda x:x%2==0,n_list)
print(n_fil)
print(list(n_fil))


#& 练习，找出列表中的数字元素
import string

n_list = ['faa','gga',10,'hello',35,45,7,8]
#? 使用filter()
n_fil = filter(lambda x:str(x).isdigit(),n_list)
print(list(n_fil))

#? 使用列表推导式
n_list = ['faa','gga',10,'hello',35,45,7,8]
n_fil = [data for data in n_list if type(data) == int or type(data) == float]
print(n_fil)


#? sorted() 排序 -- 生成一个列表,如果自定义的规则中有系统存在的函数，直接使用即可
#? 如果不存在的话，就自定义~
'''
sorted(list,reverse,key = func)
    list : 需要排序的列表
    reverse : 表示升序还是降序
    key : 自定义排序的规则
'''
#& 自定义排序规则
list2 = ['4562','daxianaaa','gagaa','r','fafaaa']
new_list= sorted(list2,reverse=True,key = lambda x:len(x))
#new_list= sorted(list2,reverse=True,key = len)
print(new_list)

#& 扩展内容：
from operator import itemgetter

#? operator模块提供的itemgetter函数用于获取对象的哪些维的数据
student_tuples = [
        ('john', 'A', 15),
        ('jane', 'B', 12),
        ('dave', 'B', 10),
        ]
n_item  =  sorted(student_tuples,key = itemgetter(2))
print(n_item)

import operator

test = [4,3,8]
b = operator.itemgetter(2)
print(b(test))


#TODO 装饰器
'''
什么是装饰器？
    概念：已知一个函数，但是不希望修改原函数，添加一些新的功能
    在Python中，这种在代码运行期间执行的机制成为装饰器[decorator]
    功能：为已经存在的函数或者类增加新的功能
    装饰器的本质：实际上就是一个闭包，只不过闭包的外部函数的参数需要被装饰的函数
    基本语法:
'''

#& 闭包方式一
def func1(a):
    def func2():
        print(a)
    func2()
func1(23)

#& 闭包方式二 - 常用形式在装饰器中
def func1(a):
    def func2():
        print(a)
    return func2
f = func1(23)
f()#

#? 装饰器的语法
#? 已经在下面的函数func，不修改原函数的基础上，增加一个新的功能print()


def func():
    print("Hello World!")
#第一步，先写一个闭包
#第二步，给外部函数设置参数，参数表示需要被装饰的函数

def outter(f):
    #? 内部函数：装饰器的核心代码
    def inner():
        #? 调用被装饰的函数
        f()
        #增加的新功能
        print("new~~~")
    #?对指定函数装饰完的整体结果，直接返回
    return inner
outter(func)()

#& outter就是装饰器的名称，inner是装饰器的核心

#? 装饰器的使用一

#& 需求：给下面显示年龄的函数增加新的功能。对年龄完成校验
#! 如果原函数有参数，在内部函数中增加新的功能的时候，如果需要对原函数的参数进行执行的运算
#! 则需要给装饰器内部函数设置参数，该参数表示从外部传递进来的值，注意传参的过程

def get_age(age):
    print(f"年龄:{age}")

def check_age(f):
    def inner(check_age):
        #校验年龄 --新功能
        if check_age < 0:
            check_age = - check_age
        f(check_age) #? -- 调用函数get_age()
        return 'abc'
    return inner
t_age = -34
check_age(get_age)(t_age)

#? 练习 
import time


def add():
    total = 0
    for ele in range(1000000):
        total +=ele

def decorator(func):
    def get_time():
        start = time.time()
        func()
        end = time.time()
        return round(end - start,3)
    return get_time

#decorator()()
print(decorator(add)())

#? 装饰器的使用二
#? 装饰器的简化使用，使用@XXX 方式使用装饰器
#? @XXX XXX表示装饰器的名称，只需要将@XXX作用于需要被装饰的函数前面即可
#! 如果使用@XXX 加载装饰器，则必须装饰器先存在,然后才能使用
#& @XXX 这种形式的装饰器多数用于系统的装饰器中
def check_age(f):
    #print("外部函数")
    def inner(check_age):
        #校验年龄 --新功能
        #print("外部函数")
        if check_age < 0:
            check_age = - check_age
        f(check_age) #? -- 调用函数get_age()
    return inner

@check_age #& 相当于check_age(get_age)
def get_age(age):
    print(f"年龄:{age}")
print(get_age) #? <function check_age.<locals>.inner at 0x000001DBA3719280>
get_age(-34)

#age = -34
#check_age(get_age)(age)

'''
@check_age 调用装饰器函数
get_age 调用内部函数
'''


#? 装饰器的使用三
#? 需求：给多个函数同时增加相同的功能，则需要给装饰器的内部函数的参数设置成不定长参数

def wrapper(f):
    print("外部~~~")
    #& args -- 元组 kwargs -- 字典
    def inner(*args,**kwargs): #! 这是一个打包的过程
        f(*args,**kwargs) #? 调用原函数，进行一个拆包的过程
        print("new~~~")
    return  inner

@wrapper # -- 每一次装饰都需要一个新的@XXX
def func1():
    print("111")
@wrapper   
def func2(a,b):
    print("222",a+b)
@wrapper
def func3(a,b,c,d):
    print("333",a+b+c+d)
func1()
func2(2,3)
func3(2,3,4,5)


#& 面试题：统计任意函数的执行时间
import time


def decorator(func):
    def get_time(*args,**kwargs):
        start = time.time()
        func(*args,**kwargs)
        end = time.time()
        return round(end - start,3)
    return get_time

#?函数递归：函数调用自身
'''
处理递归的关键：
    <1>  需要找到一个临界值：[让程序停止下来的条件]
    <2>  函数相邻两次调用之间的关系
'''
#! 恶意调用
def func():
    print("ok")
    func()
func()

#& 斐波那契数列
count = 0
def func(n):
    global count
    count +=1
    if n == 1 or n == 2:
        return   1
    return func(n-1) + func(n-2)
print(func(4))
print(count)

#& 使用递归求1 - 某个数之间所有整数的和
def func(num):
    if num == 1:
        return  1
    else:
        return func(num-1) + num
print(func(100))


#TODO 包和模块
'''
包[package]是一种管理Python模块命名空间的形式，采用‘点模块名称’
package 本质是一个文件夹[目录] 但是特殊之处在于：该文件夹下有一个 __init__.py 代表初始化
但是前期空的，后期在项目开发中，会在其中书写一些项目的配置信息
#! 实际上，一个py文件就是一个模块，一个模块名包含包名
import  aa.file aa表示包名或者目录名 file是文件名
'''
#? 使用包 -- 在一个py文件中访问其他py文件中 的变量需要导入，默认是当前的根目录
#? 使用点语法，点表示路径的层级关系
#? 自定义标记开始的路径
import package.module

print(package.module.num)

#& 自定义模块一
'''
import 包1,包2， py文件名 as 变量名
import package.module,package.XXX 一起导入，逗号隔开
'''

import package.module as f

#? 调用函数
f.func()


#& 自定义模块二
#& 方式一
from package.module import func, num

print(num)
func()
#? 使用以上方式，访问变量或者调用函数的时候，无需多次声明模块的路径
#? 缺点：如果当多个模块中出现同名的变量，函数或者类，就会出现覆盖问题

#& 方式二
from package.module import *

print(num)
func1()

import random

#& dir() 函数 获取一个对象的所有内容，包括变量，函数，类
#? 作用：可以当做一个工具使用，可以查看一个模块，变量，对象中的所有内容
import package.module

#print(random)
#print(dir(random))
print(dir(package.module))
print(dir("1222"))

#& __name__ 获取当前正在执行的模块的名称
#? 如果当前文件运行，则 __name__ 的值为__main__ ，如果是其他文件在运行，当前文件是被导入的，则__name__的值为模块名称

import package.module as f

#? 运行结果
'''
end~~
package.module
'''
f.func1()
#? 使用场景：如果封装一个模块，在模块本身中如果要进行函数代码测试
#? 则可以将测试代码书写到 if __name__ == "__main__":
#? 当在其他文件导入该模块时，只需要调用需要的内容即可,if中的的代码被执行

#TODO 系统模块
#? time模块
'''
UTC 格林威治天文时间，世界标准时间，在中国为UTC+8
DST 夏令时 表示时间的显示格式
'''
#& 时间的表示形式
#? [时间戳] [时间元组格式] [格式化时间字符串]
from time import *

#? time() 获取当前时间的时间戳
t1 = time()
print(t1)
#>> 1651595084.7720945

#? gmtime() 获取当前的UTC，为元组形式
t2 = gmtime()
print(t2)
#>> time.struct_time(tm_year=2022, tm_mon=5, tm_mday=3, tm_hour=16, tm_min=41, tm_sec=51, tm_wday=1, tm_yday=123, tm_isdst=0)

#? localtime() 获取当地时间，为元组形式
t3 = localtime()
print(t3)
#>> time.struct_time(tm_year=2022, tm_mon=5, tm_mday=4, tm_hour=0, tm_min=27, tm_sec=36, tm_wday=2, tm_yday=124, tm_isdst=0)

#? mktime() 将时间的元组形式转换为时间戳
t4 = mktime(t3)
print(t4)
#>> 1651595496.0

#? 将时间戳转换为时间的字符串，默认格式
t5 = ctime(t1)
print(t5)
#>> Wed May  4 00:32:34 2022

#? 将时间元组形式转为时间的字符串，默认格式
t6 = asctime(t3)
print(t6)
#>> Wed May  4 00:33:50 2022

#? strftime() 将时间的元组形式转换为时间的字符串，可以自定义格式
#? %Y-%m-%d-%H-%M-%S
t7 = strftime("%Y-%m-%d %H:%M:%S",t3)
print(t7)
#>> 2022-05-04 00:37:12

#? strptime() 将时间的字符串形式转换为时间元组形式，也可以理解为解析时间字符串
#! 解析时间字符串的时候，一定要注意事件字符串和时间格式之间的完全匹配
t8 =  strptime("2022-05-04 00:37:12","%Y-%m-%d %H:%M:%S")
print(t8)

#? sleep() 将程序进入阻塞状态，指定时间到达之后，会自动接触阻塞，程序会继续向下执行
#? 使用场景：使用在进程和线程中较多

#? calendar 模块
import calendar

#print(calendar)

#&判断是否是闰年
print(calendar.isleap(2020))
#>> True

#& 获取指定年指定月的万年历
import calendar

print(calendar.month(2022,5))
'''
      May 2022
Mo Tu We Th Fr Sa Su
                   1
 2  3  4  5  6  7  8
 9 10 11 12 13 14 15
16 17 18 19 20 21 22f
23 24 25 26 27 28 29
30 31
'''
#& 获取指定年的万年历
import calendar

print(calendar.calendar(2022))

#& 获取指定日期的星期 取值范围是0 - 6
import calendar

print(calendar.weekday(2022,5,4))

#* 获取指定年份之间闰年的个数
import calendar

print(calendar.leapdays(2000,2022))


#& string 模块
import string

print(string.digits)
print(string.ascii_uppercase)
print(string.ascii_lowercase)
print(string.ascii_letters)
print(string.octdigits) #?八进制
print(string.hexdigits) #?十六进制
print(string.punctuation) #? 所有标点符号
print(string.whitespace) #? 所有的空白符
print(string.printable) #?所有可以打印的字符

#& datetime 模块 是time模块基础上的二次封装
import datetime

'''
date -- 年月日
time -- 时分秒
datetime -- 年月日时分秒
tzinfo 时区
'''

#* 获取当前时间
import datetime

d1 = datetime.datetime.now()
print(d1)
#>> 2022-05-04 01:12:20.267430
print(type(d1))
#>> <class 'datetime.datetime'>

#* 将时间转为字符串
#? %Y-%m-%d %H:%M:%S
import datetime

d2 = datetime.datetime.now()
time_str = d2.strftime("%Y-%m-%d %H:%M:%S")
print(time_str)
#>> 2022-05-04

#* 格式字符串转换为时间格式
d3 = datetime.datetime.strptime(time_str,"%Y-%m-%d %H:%M:%S")
print(d3)
print(type(d3))
#>> <class 'datetime.datetime'>

#* 两个时间对象之间可以进行减法运算
import datetime

date1 = datetime.datetime(2022,5,2,7,5,30,100)
date2 = datetime.datetime(2022,5,3,14,15,30,100)
#print(date1+date2)
#>> TypeError: unsupported operand type(s) for +: 'datetime.datetime' and 'datetime.datetime'
d4 = date2 - date1
print(d4)
#>> 1 day, 7:10:00
print(d4.days)#>> 属性
print(d4.seconds)

#? os 模块
#& listdir(path) 列出指定路径下的所有的内容
import os

#path = r"git-demo\PythonCode"
r1 = os.listdir(path="git-demo\PythonCode")
print(r1)

#& mkdir(path) 创建一个目录
#& open() 创建一个文件
f1 = open(r"uziheihei.txt","w",encoding="utf-8")

#& rmdir(path) 删除一个目录
#& remove(path) 删除一个文件夹
import os

os.remove(r"uziheihei.txt")

#& rename(old,new) 给指定的文件或者文件夹重命名


#& os.path 拼接路径

#& 使用+ 注意在window中 路径是 \\ 不推荐使用，因为不同操作系统下的的拼接方式不同
path = r"D:\Git-Space\git-demo\PythonCode\nsetMath.py"
subpath = r"git-demo\PythonCode\PythonBase.py"
new_path = path+"\\"+subpath
print(new_path)
#>> D:\Git-Space\git-demo\PythonCode\nsetMath.py\git-demo\PythonCode\PythonBase.py

#& 使用join() -- 自动识别平台
import os

path = r"D:\Git-Space\git-demo\PythonCode\nsetMath.py"
subpath = r"git-demo\PythonCode\PythonBase.py"
new_path  = os.path.join(path,subpath)
print(new_path)

#& split(path) 分割 -- 得到的是一个元组
import os

path = r"D:\Git-Space\git-demo\PythonCode\nsetMath.py"
r1 = os.path.split(path)
print(r1)
#>> ('D:\\Git-Space\\git-demo\\PythonCode', 'nsetMath.py')

#& splitext(path) 对路径进行分割，结果为一个元组，格式（父路径，扩展名）
#? 如果path是一个文件夹，返回（父路径,""）
#? 如果path是一个文件，返回（父路径,".扩展名"）
import os

path1 = r"git-demo\PythonCode"
path2 = r"git-demo\PythonCode\PythonBase.py"
print(os.path.splitext(path1))
#>> ('git-demo\\PythonCode', '')

print(os.path.splitext(path2))
#>> ('git-demo\\PythonCode\\PythonBase', '.py')


#& isfile(path) 判断指定路径是否是文件 True -- False
#& isdir(path) 判断指定路径是否是文件夹 True -- False
#& exists(path) 判断path 是否存在 True -- False
#& getsize(path) 获取一个文件的字节大小，常用于文件读写中
import os

path2 = r"git-demo\PythonCode\PythonBase.py"
print(os.path.getsize(path2))
#>> 33331




#TODO 面向对象

#? 面向对象是给予万物皆对象这个哲学观点。在Python当中，一切皆对象
#? 面向过程就是解决问题的主体对象是本身，并且有些顺序是不可颠倒的。
# ---------------------------------------------------------
#? 面试题：面向过程和面向对象的区别；
'''面向过程，一种看待问题的思维方式，着眼于问题是怎样一步一步解决，然后去亲力亲为地区解决问题；
代码中从上而下顺序执行，模块之间的关系尽可能简单，在功能上相对独立，程序的流程在一开始就已经决定'''
# ---------------------------------------------------------
'''面向对象：着眼于找到[一个具有特殊功能的具体个体，然后委托这个个体去解决问题] ;
在程序中，把数据及对数据的操作方法反正该仪器，作为一个相互依存的整体；
对同类对象抽象出共性，形成类；类中的大多数数据，只能用本类的方法进行处理，程序流程由用户在使用中决定
面向对象开发，先要去找就有所需功能的对象，如果不存在，那么就可以创建一个'''

# ---------------------------------------------------------
#? 面向过程和面向对象的优缺点
''' 面向对象：易维护，易复用，易扩展；也就是封装，继承，多态的特性。
可以设计出低耦合的系统，使得系统更加灵活，更加易于维护
缺点：性能比比较低'''
# ---------------------------------------------------------
#? 什么是类？[一个具有特殊功能的实体的结合] -- 群体，是抽象的
'''什么是对象？ 在一个类中，一个具有特殊功能的实体，能够帮忙解决特定的问题[对象也被成为实例]，
是具体的两者之间的关系；对象是类的具体存在;'''
# ---------------------------------------------------------

a = NameError()
print(type(a))

#? 类的自定义和对象的创建
'''
class 类名():
    类体

类名：遵循大驼峰命名法，尽量使用单个或者多个有意义的单词连接而成
类体：包含特征和行为，对应地就是变量和函数
注意事项:
    1>py文件可以作为一个模块；在实际项目开发中，如果需求较为复杂，类的定义建议一个py文件
    2> 当一个类定义完成之后，类中的内容被称为类体，又被称为类的成员，当类被加载的时候，其中的成员也会被加载
'''
class HUMAN():
    print(111)

#? 创建对象
a = HUMAN()
print(a)
#>> <__main__.HUMAN object at 0x0000019E618B3F70>
print(type(a))
#>> <class '__main__.HUMAN'>

#& 创建对象的过程，本质上就是定义变量的过程，该变量中存储的是创建出来的对象
#& 打印一个自定义类创建的对象，默认情况下，打印的是该对象再内存空间中的地址
#& 一个普通的类，可以创建无数个对象，每个对象在内存空间中拥有独立的地址
#& 类名 表示创建对象，但是改代码执行一次，就表示创建一个对象


#? 构造函数
'''
先创建对象，然后使用直接赋值[动态绑定属性]方式给对象绑定属性，可以使用，但是代码比较繁杂，
一般情况下，很多类倾向于将对象创建为有初始状态的，在类中可以定义一个函数，名称为__init__
该特殊的函数被称为构造函数，主要用于创建对象并将对象的数据做出初始化

强调：构造函数包括 __new__  __init__
注意：在Python中，以__xxx__ 方式命名的函数被称为魔术函数/魔术方法，这类函数在使用时候无法手动调用，在进行某个操作的时候会自动调用
构造函数，也被称为构造器,指的是创建对象的时候，被自动调用的函数

def __init__(self):
    函数体

def __new__(cls,*args,**kwargs):
    pass
'''


#? 没有使用构造函数的定义类 比较繁琐，每个对象都要需要动态绑定
#? 需要使用构造函数来完成对象创建，减少代码的繁琐
class Person():
    #? 对象属性的限制绑定
    __slots__ = ('P_name','P_sex','P_age','P_height')

    #? 构造函数__init__  自动调用
    def __init__(self,P_name,P_age,P_sex,P_height) -> None:
        print("构造函数被调用了~~")
        self.P_name = P_name
        self.P_age = P_age
        self.P_sex = P_sex
        self.P_height = P_height

    #? 自定义函数 show()
    def show(self):
        print(f"姓名:{self.P_name},年龄:{self.P_age},性别:{self.P_sex},身高:{self.P_height}")

#>> P1 = Person() 没有传递参数
#>> TypeError: __init__() missing 2 required positional arguments: 'P_name' and 'P_age'

P1 = Person("Jack",10,25,173)
P1.show()

'''
P1 = Person()
P1.P_name = "张三"
P1.P_age = 18

P2 = Person()
P2.P_name = "张三"
P2.P_age = 18

P3 = Person()
P3.P_name = "张三"
P3.P_age = 18
'''

'''
注意:
    1> 如果需要给对象动态绑定属性的时候，一般都是通过__init__ 定义
    2> 当创建对象的时候，一定要注意和 __init__ 的参数匹配的问题
    3> __init__ 的时候，可以达到代码的简化
'''

#? 构造函数的工作原理
'''
    1> 真正意义上，构造函数包含 __new__ 和 __init__
    2> 在实际开发中 常用__init__ 因为 __new__ 有没有定义出现，当创建对象的时候都会自动调用
    3> __new__ 表示创建对象,该函数的返回值就是创建出来的对象
        __init__ 表示初始化对象，系统将__new__ 的返回值[对象]会自动传参给__init__中的self
        完成对象的初始化
    4>构造函数中，当创建对象的时候，都是先调用__new__ 然后才调用 __init__
'''

#& __init__ 和 __new__ 的配合使用

class Animal():
    __slots__ = ("name","age")
    #>> __new__ 在单例模式中会用到
    def __new__(cls,*args,**kwargs):
        print("new~~~")
        return super().__new__(cls)#& 表示创建一个对象

    def __init__(self,name,age) -> None:
        print("init~~~")
        self.name = name
        self.age = age
animal = Animal("Jack",24)
print(animal) 
print(animal.name,animal.age)
'''
new~~~
init~~~
<__main__.Animal object at 0x000001D6DC5E4BB0>
Jack 24
'''

#& 构造函数的练习使用
class Number():
    __slots__ =("n","m")
    def __init__(self,n,m) -> None:
        self.n = n
        self.m = m  
    def __new__(cls,*args,**kwargs):
        return super().__new__(cls)

    #? 自定义方法
    def addition(self):
        return self.n+self.m

    def subtration(self):
        return self.n - self.m

    def multiplication(self):
        return self.n * self.m

    def division(self):
        return round(self.n / self.m,3)

new_num = Number(20,23)
print(new_num.addition())
print(new_num.subtration())
print(new_num.multiplication())
print(new_num.division())

#& 圆的面积和周长 以及判断点和该圆的关系
import math


class Circle():
    __slots__ = ("r","cen_point")
    def __init__(self,r,cen_point) -> None:
        self.r = r
        self.cen_point = cen_point
    def area(self):
        return round(2*math.pi*self.r,2)

    def perimeter(self):
        return round(math.pi*(self.r ** 2),2)

    def distance(self,point):
        #>> 计算点P和圆心的距离
        #& self.cen
        dis =  math.sqrt((self.cen_point.x - point.x)**2 + (self.cen_point.y - point.y)**2)
        if  dis > self.r:
            return "点P在圆外"
        elif dis == self.r:
            return "点P在圆上"
        elif dis < self.r:
            return "点P在圆内"

class Point():
    __slots__ = ("x","y")
    def __init__(self,x,y) -> None:
        self.x = x
        self.y = y
    
#? 圆心坐标
center_point = Point(23,10)
#? 创建圆
cir = Circle(2,center_point)
#? 点P
p_oint = Point(25,19)

print(f"圆的周长是 {cir.area()}")
print(f"圆的面积是 {cir.perimeter()}")
print(cir.distance(p_oint))

#? 类属性和实例属性
#>> 类属性[类的字段] 和实例属性[对象属性，对象的字段]的区别
'''
    1> 定义位置不同：类属性直接定义在类中，实例属性定义在 __init__中或者在类的外面直接动态绑定
    2> 访问方式不同，类属性可以通过类名或者对象访问，而实例属性只能通过对象访问
    3> 访问优先级不同 当类属性和实例属性重名是，通过对象访问，优先访问的是实例属性
    4> 在内存中出现的时机不同：类属性优先于实例属性出现在内存中，类属性随着类的加载而出现，实例属性是对象创建完毕之后才出现
    5> 使用场景不同: 类属性用于表示多个对象共享的数据，实例属性表示每个对象特有的数据
'''

class Person():
    #? 类属性
    num = 10
    name = "Uzi"
    age = 12
    data = "123"
    def __init__(self,name,age) -> None:
        #? 实例属性/对象属性
        self.name = name
        self.age = age
        self.num = 20
per = Person("Jack",18)
#? 实例属性/对象属性
per.hobby = "dance"

#?访问 -- 类属性
print(Person.num)
print(per.num)
'''
10
10
'''
#?访问 -- 实例属性
print(per.name,per.age,per.hobby)
#>> Jack 18 dance

#? 访问优先级 - 优先是对象属性
print(per.num) 
#>> 20
del  per.num
print(per.num)
#>> 10

#? 类属性和实例属性
print(Person.data) #>> 123
print(per.data)  #>> 123

per.data = "hello" #! 这个地方并不是修改类属性，而是给per对象动态绑定了一个实例属性data
print(per.data) #>> hello
print(Person.data) #>> 123

#& 由此 对象.属性 = 值 都表示给当前对象动态绑定属性，属于实例属性，哪怕该属性和类属性重名，也表示实例属性
#& 如果修改类属性的值，只能通过类名访问修改
Person.data = "hello"
print(Person.data) #>> hello

#? 应用场景的区别

class Student():
    #? 类属性
    school_name = "千峰教育"
    student_pro = "Python"
    __slots__ = ("name","sex","age","professional")
    def __init__(self,name,sex,age,professional) -> None:
        #? 实例属性
        self.name = name
        self.sex =sex
        self.age = age
        self.professional = professional

stu1 = Student("uzi","男",23,"软件工程")
stu2 = Student("ming","男",24,"计算机科学与技术")
print(Student.school_name,Student.student_pro)
print(stu1.school_name,stu1.student_pro)
#>> 千峰教育 Python
#>> 千峰教育 Python

#? 析构函数
'''
和构造函数相反，当对象被销毁的时候自动调用的函数，成为析构函数，函数名为__del__
'''
#& 析构函数__del__ 调用的时机 -- 当对象被销毁的时候

class Person():
    def __init__(self) -> None:
        print("构造函数被调用了~~")
    def __del__(self) -> None:
        print("析构函数被调用了~~")
#? 当对象被定义为全局变量，程序结束之后，__del__ 就会被调用
print("start")
p1 = Person()
print("end")
'''
start
构造函数被调用了~~
end
析构函数被调用了~~
'''

#? 当对象被定义为局部变量，函数调用完毕，__del__ 就会被调用
class Person():
    def __init__(self) -> None:
        print("构造函数被调用了~~")
    def __del__(self) -> None:
        print("析构函数被调用了~~")
def func():
    p1 = Person()

print("start")
func()
print("end")
'''
start
构造函数被调用了~~
析构函数被调用了~~
end
'''

#? 当对象被del 则__del__ 会被自动调用
class Person():
    def __init__(self) -> None:
        print("构造函数被调用了~~")
    def __del__(self) -> None:
        print("析构函数被调用了~~")

print("start")
p1 =Person()
del p1
print("end")
'''
start
构造函数被调用了~~
析构函数被调用了~~
end
'''

#TODO 封装
''''
面向对象的三大特征  封装，继承，多态
广义的封装: 函数的定义和类的定义
狭义的封装: 一个类中的某些属性，如果不希望被外界字节访问，则可以将被该属性私有化，该属性只能在当前类中被直接访问，如果在类的外面需要访问[获取或者修改] 则可以通过暴露给外界的函数间接访问
封装的本质：将类中的属性私有化
'''

#& 私有属性[重点掌握]

#? 属性未被私有化[公开属性]
class Person():
    def __init__(self,name,age) -> None:
        self.name = name
        self.age = age
    def show(self):
        print(f"{self.name},{self.age}")

per1 = Person('uzi','24')
#? 直接访问
print(per1.age,per1.name)
per1.show()

per1.name = "ming"
per1.age = 25
print(per1.age,per1.name)

#? 属性私有化[私有属性]
class Person():
    def __init__(self,name,age) -> None:
        #? 只需要在属性名的前面添加两个下划线，就该属性时私有属性；
        #? 私有属性适用于类属性和实例属性
        self.__name = name
        self.__age = age
    #? 私有属性无法在类的外面通过对象直接访问，但是在类中可以直接被访问
    def show(self):
        print(f"{self.__name},{self.__age}")
per2 = Person('uzi',25)
#>> print(per2.__name)
#>> print(per2.__age)
#>> AttributeError: 'Person' object has no attribute '__name'
per2.show()

'''
私有属性：
一个属性一旦被私有化，在内存中会用另外一个形式形式存在，_类名__属性名
但是，不建议使用，主要因为根据不同的操作系统，不同的Python版本，在内存中存在的形式可能不同
'''
print(per2._Person__name) #>> uzi
print(per2._Person__age) #>> 25

#? 暴露给外界的函数间接访问
#? 1>  通过get set 函数

class Person():
    def __init__(self,name,age) -> None:
        self.__name = name
        self.__age = age
    def show(self):
        print(f"{self.__name},{self.__age}")

    '''
    对于属性的访问，无非就是获取和修改，传统的做法，就是设置get，set 常常用于Java
    get 函数：获取私有属性的值，所以给该函数设置返回值
    set 函数，修改私有函数的值
    '''
    def get_person(self):
        return self.__name,self.__age

    def set_person(self,name,age):
        self.__age = age
        self.__name = name
    
per3 = Person('uzi',25)
per3.show() #>> uzi,25
print(per3.get_person())
per3.set_person("ming",26)
per3.show() #>> ming,26

#? 可以通过装饰器进行访问私有属性

class Person():
    def __init__(self,name,age) -> None:
        self.__name = name
        self.__age = age
    def show(self):
        print(f"{self.__name},{self.__age}")

    
    '''
    @property 该装饰器装饰的函数会被当做属性处理 可以单独使用
    '''
    @property
    def name(self):
        return self.__name

    '''
    @xxx.setter 该装饰器装饰的函数也会被当做属性处理，该函数的函数名相当于是一个变量名
    主要用于给私有属性重新赋值
    #! xxx表示被@property装饰的函数的函数名
    #& 建议保持一致，命名为私有化属性的属性名
    '''
    @name.setter
    def name(self,name):
        self.__name = name

per3 = Person('uzi',25)
per3.show()
print(per3.name) #>> ['uzi', 25] 调用@property 修改的函数
per3.name = "ming" #& 修改 @get_person.setter的函数
print(per3.name) #>>ming


#? 解释下面不同形式的变量出现在类中的意义
''''
a : 公开属性
_a : 在类的外面可以直接访问，但是不建议使用，容易和私有属性混淆
__a : 私有属性 只能在类的内部被直接访问，可以暴露给外界的函数访问
__a__ : 在类的外面可以直接访问，但是不建议使用。因为系统属性和魔术方法都是这种形式的命名
'''

#& 私有函数
class Person():
    #? 公开函数
    def func(self):
        print("func ~~ 11")
        self.__show()

    #? 私有函数
    def __show(self):
        print("show ~~ 22")


p = Person()
p.func()
#>> func ~~ 11
#>> show ~~ 22
'''
  1> 如果一个函数需要私有化，则可以在类中定义一个公开函数，在公开函数中调用私有函数即可~
  2> 类中的实例函数之间相互调用，self.函数名()
  3> 如果封装的功能只希望在类的内部使用, 外部不希望被访问
'''

#& [面试题]
class Check():
    @property
    def show(self):
        print("showing")
        return 10
c = Check()
#& c.show() #>> TypeError: 'NoneType' object is not callable
print(c.show) #>> 调用show函数  获取show函数返回值
'''
showing
10
'''

#TODO 继承

#? Person() 作为父类（超类，基类）
#? object 是一个系统类，是所有类的根类
class Person(object):
    def __init__(self,name,age) -> None:
        self.name = name
        self.age = age
    def show(self):
        print("showing")
#>> 单继承
#? Teacher Doctor Student 作为子类，派生类
class Teacher(Person):
    def __init__(self,name,age,subject) -> None:
        self.name = name
        self.age = age
        self.subject = subject   
    def teacher(self):
        print("teaching")    
    # def show(self):
    #     print("showing")
class Doctor(Person):
    # def __init__(self,name,age) -> None:
    #     self.name = name
    #     self.age = age
    def assist(self):
        print("assisting")
    # def show(self):
    #     print("showing")
class Student(Person):
    def __init__(self,name,age,score) -> None:
        self.name = name
        self.age = age
        self.score = score
    # def show(self):
    #     print("showing")

#? [面试题] 继承的特点
'''
1> 简化了代码
2> 提高了代码的复用性
3> 提高了代码的可扩展性
'''

#? 父类Person()
class Person(object):
    def __init__(self,name,age) -> None:
        self.name = name
        self.age = age
    def show(self):
        print(f"showing,name:{self.name},age:{self.age}")

#& 子类中没有定义构造函数
'''
1> 当子类中未定义构造函数，则默认情况下，创建子类对象调用的是父类中构造函数
2> 通过子类创建的对象，可以访问父类中未被私有化的属性和调用分类中未被私有化的函数
3> 如果子类中定义了构造函数，则子类中的构造函数会覆盖父类中的构造函数
当创建子类对象的时候，优先调用的是子类的构造函数
4> 如果子类中定义了构造函数，默认情况下，子类对象无法访问父类中定义的公共属性
所以需要在子类的构造函数中调用父类的构造函数
'''
class Student(Person):
    pass

#? stu = Student()
#>> TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'
stu =Student("ming",10)
stu.show()
#>> showing,name:ming,age:10
print(stu.name,stu.age)
#>> ming 10

class Doctor(Person):
    def assist(self):
        print("assisting")
doc = Doctor("Li",25)
doc.show()
doc.assist()
#& showing,name:Li,age:25
#& assisting

#& 在子类中定义了构造函数
class Teacher(Person):
    def __init__(self,name,age,subject) -> None:
        #? 调用父类的构造函数
        #& 方式一
        #& super(Teacher,self).__init__(name,age)

        #& 方式二 -- 多用于单继承
        #? super().__init__(name,age)

        #& 父类类名调用 -- 使用较多 常用于多继承
        Person.__init__(self,name,age)
        self.subject = subject   
    def teacher(self):
        print("teaching")  

tea = Teacher("Hu",32,"Math")
#? tea.show()#>> AttributeError: 'Teacher' object has no attribute 'name'
tea.teacher()
tea.show()
'''
teaching
showing,name:Hu,age:32
'''

#? __slots__  的继承问题
'''
在父类定义的 __slots__ 只能对当前类起作用，无法对子类进行限制
如果子类也需要和父类相同的属性限制，则需要在子类中手动设置 __slots__ 的值
'''
class Person(object):
    __slots__ = ("name","age")

class Worker(Person):
    pass
p = Person()
p.name = "UZI"
p.age = 23
print(p.name,p.age)
#&p.hobby = "dance" #>> AttributeError: 'Person' object has no attribute 'hobby'

w = Worker()
w.name = "222"
w.age = 20
w.hobby = "sing"
print(w.name,w.age,w.hobby) #>> 222 20 sing

#? 继承中的类属性
'''
    1> 如果子类中未定义，则继承父类，当父类中的类属性更改，子类的类属性随着更改
    2> 如果子类中定义，则互不影响
'''
class Animal(object):
    num = 10
class Dog(Animal):
    pass
print(Animal.num,Dog.num) #>> 10 10 
Animal.num = 66
print(Animal.num,Dog.num) #>> 66 66
Dog.num = 100
print(Animal.num,Dog.num) #>> 66 100
Animal.num = 888
print(Animal.num,Dog.num) #>> 888 100

#TODO 多继承
#? 父类
class SuperClass1(object):
    def __init__(self,num) -> None:
        self.num = num

    def func1(self):
        print("1111")

    def show(self):
        print("show-111")
class SuperClass2(object):
    def __init__(self,a:int,b:int) -> None:
        self.a = a
        self.b = b
    
    def func2(self,x:int):
        print("2222")

    def show(self):
        print("show-222")

#& 子类未定义构造函数
'''
    1> 如果一个子类继承自多个父类，子类中未定义构造函数
        默认情况下，当创建子类对象的时候，调用的是父类列表中第一个父类中的构造函数
'''
class SubClass1(SuperClass2,SuperClass1):
    pass

sub1 = SubClass1(2,3)
#>> TypeError: __init__() missing 1 required positional argument: 'num'
print(sub1.a,sub1.b)
#print(sub1.num) #>> AttributeError: 'SubClass1' object has no attribute 'num'

sub1.func1()
sub1.func2(3)
'''
1111
2222
'''
#& 子类中定义构造函数
'''
    1>如果一个子类继承自多个父类，子类中定义构造函数
    而且子类需要继承父类中所有的属性，则只需要在子类的构造函数中调用父类的构造函数
    2> 如果多个父类中有同名的函数，通过子类对象调用，调用的是父类列表中的第一个父类中的函数
'''
class SubClass2(SuperClass1,SuperClass2):
    def __init__(self,num,a,b,kind) -> None:
        #? super().__init__(num) -- 只可以单继承，父类列表的第一个
        SuperClass1.__init__(self,num)
        SuperClass2.__init__(self,a,b)
        self.kind = kind
sub2 = SubClass2(13,45,6,67)
print(sub2.a,sub2.b)
print(sub2.num)
print(sub2.kind)
sub2.func1()
sub2.func2(3)
'''
45 6
13
67
1111
2222
'''
sub2.show() #& show-111

#TODO 函数的重写 override
#& 自定义函数重写
'''
    重写：override，在继承的前提下，如果在子类中重新实现了父类的函数
    #>> 什么时候需要重写函数
        1> 如果一个类有很多的子类，一部分子类可以直接使用父类中的函数实现的功能
        但是，如果父类中实现的需求满足不了个别子类的使用，则需要在子类中重写父类的函数
    #>> 重写需要注意的事项
        保留函数的声明部分
        重新实现函数的实现部分
'''
class Animal():
    def show(self):
        print("父类---show")
    def func(self):
        print("父类---func")
    
    def walk_style(self):
        print("走路")

class Cat(Animal):
    def func(self):
        print("子类---func")
class Dog(Animal):
    pass
class Monkey(Animal):
    pass
class Bird(Animal):
    def walk_style(self):
        print("飞行")

cat = Cat()
cat.show() #? 调用父类中的函数
cat.func() #? 子类---func

bird = Bird()
bird.walk_style() #? 飞行


#& 系统函数重写
class Person(object):
    __slots__ = ("name","age","hobby")
    def __init__(self,name,age,hobby) -> None:
        self.name = name
        self.age = age
        self.hobby = hobby
    #? __str__ 必须设置返回值，并且返回值必须是字符串类型
    #? 一般情况下,__str__ 的返回值为和对象相关的属性信息
    def __str__(self) -> str:
        return f"{self.name},{self.age},{self.hobby}"

    #? 打印容器的时候自动调用
    def __repr__(self):
        return f"{self.name},{self.age},{self.hobby}"

    #? 可以简写成：__repr__ = __str__

per1 = Person("UZI",23,"ADC")
#? 工作原理：当打印对象的时候，会自动调用object类中的__str__,
#? 返回值默认是当前对象在内存中的地址
#>> <__main__.Person object at 0x000002164F7A1C00>
print(per1) 
#? TypeError: __str__ returned non-string (type NoneType)
#& print(per1.__str__()) 
'''
UZI,23,LOL
UZI,23,LOL
'''
per2 = Person("Ming",24,"SUP")
print(per2)
perlist = [per1,per2]
#? [<__main__.Person object at 0x00000151DC7640C0>, <__main__.Person object at 0x00000151DC764100>]
print(perlist)
#? [UZI,23,ADC, Ming,24,SUP]

#TODO 运算符重载 overload
'''
    对于自定义的类，通过该类创建的的对象，不支持某些运算符的使用，则可以重载对应的函数
    比如算术运算符，关系运算符
'''
#& +:__add__ * __mul__
'''
+:__add__ 
* __mul__
>:__gt__  
<:__lt__ 
==: __eq__ 
!=: __ne__
>= : __ge__
<= : __le__
print(34+3.6)
'''
#? 字符串，列表，元组，能够支持运算，底层工作原理是  调用了__add__ 函数
#? 能支持某些运算的，底层都是有魔术方法的
print("hello"+"world")
print(["UZI"]+["Ming"])
print(("UZI",)+("Ming",))

#? 重写 __add__
#! 当__add__ 返回一个对象，直接打印，得到的结果是该对象在内存中的地址
class Person(object):
    def __init__(self,age) -> None:
        self.age = age
    def __add__(self,other):
        #return self.age+other.age #得到一个int类型
        return Person(self.age+other.age)
    def __str__(self) -> str:
        return str(self.age)

p1 = Person(10)
p2 = Person(20)
#>> TypeError: unsupported operand type(s) for +: 'Person' and 'Person'
#? print(p1+p2)
#? print(dir(p1))

print(p1+p2)
print(p1.__add__(p2)) #? return self.age+other.age --> int 类型
'''
30
30 
'''
print(p1+p2)
print(p1.__add__(p2))
print(p1.__add__(p2).age)
'''
<__main__.Person object at 0x0000023AA5903E50>
<__main__.Person object at 0x0000023AA5903E50>
30
'''
print(p1+p2)
print(p1.__add__(p2))
print(p1.__add__(p2).age)
'''
30
30
30
'''

#TODO 对象的内置属性

#& __slots__ 限制对象属性的动态添加
#& __doc__  document文档，表示类的描述信息，获取类中的文档注释[多行注释]

class MyClass(object):
    '''
    自定义类
    '''
    def func(self):
        print(1111)
print(MyClass.__doc__)
m = MyClass()
print(m.__doc__)

#& __dict__ 获取类或者对象的所有信息[属性或函数] 返回一个字典
class Test(object):
    #? 类属性
    num = 10
    #? 构造函数
    def __init__(self,name) -> None:
        self.name = name
    
    #? 实例函数
    def func(self):
        pass
    
    #? 类函数
    @classmethod
    def func2(cls):
        pass

    #? 静态函数
    @staticmethod
    def func3():
        pass
print(Test.__dict__)
test = Test("UZI")
print(test.__dict__)
'''
{'__module__': '__main__', 'num': 10, '__init__': <function Test.__init__ at 0x00000161895F91F0>, 'func': <function Test.func at 0x00000161895F9280>, 'func2': <classmethod object at 0x0000016189393E20>, 'func3': <staticmethod object at 0x0000016189393E80>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None}
{'name': 'UZI'}
'''
#? 对象.__dict__  只能获得对象属性，实例属性
#? 类名.__dict__ 可以获取类中的类属性，构造函数，实例函数，类函数和静态函数


#& __module__ 获取当前操作的对象在哪个模块，如果在当前模块，则返回值为__main__

class Test(object):
    #? 类属性
    num = 10
    #? 构造函数
    def __init__(self,name) -> None:
        self.name = name  
    #? 实例函数
    def func(self):
        pass
    #? 类函数
    @classmethod
    def func2(cls):
        pass
    #? 静态函数
    @staticmethod
    def func3():
        pass
print(Test.__module__)
test = Test("UZI")
print(test.__module__)
'''
__main__
__main__
'''
#& 如果对象在其他模块，则返回值为模块名
from code import p1

print(p1.__module__) #? code

#& __name__  获取当前正在执行的模块的名称，如果执行的是当前模块，则返回值__main__ 
import code

print(__name__)

#&  __class__ 类似于 type() 获取指定对象的数据类型
print(test.__class__)
print(type(test))
'''
<class '__main__.Test'>
<class '__main__.Test'>
'''

#& 对象的内置函数

#? id() 获取一个对象在内存中的地址
#? type() 获取一个对象的数据类型，返回<class '类型'>
'''
1> 如果类型是自定义的类，则结果<class '__main__.类名'>
2> 用于比较
'''
print(type(10) == int)
print(type(10) == type(20))
'''
True
True
'''

#& 练习
l1 = [34,6,6,"as","4q5","fa78",56,7,True,"gsg"]
l2 = [ele for ele in l1 if type(ele) == str]
print(l2)
#>> ['as', '4q5', 'fa78', 'gsg']

#? isinstance()  判断一个对象的类型是否是指定的类型，和type() 可以相互转化
print(isinstance(10,int)) #>> True
l1 = [34,6,6,"as","4q5","fa78",56,7,True,"gsg"]
l3 = [ele for ele in l1 if isinstance(ele,str)]
print(l3)
#>> ['as', '4q5', 'fa78', 'gsg']

#? dir() 获取一个对象的所有信息，包含函数和属性
print(dir("abc"))
print(dir([1,2]))

#? issubclass(类1,类2) 判断两个类之间是否具有继承关系,返回值True 
class Animal(object):
    pass
class Cat(Animal):
    pass
class SmallCat(Cat):
    pass
print(issubclass(SmallCat,Cat))
print(issubclass(SmallCat,Animal))
print(issubclass(SmallCat,object))
print(issubclass(Cat,Animal))
'''
True
True
True
True
'''


#TODO 类方法和静态方法
'''
类方法、类函数，类属性
实例函数 ，实例属性

静态函数
[面试题] 实例函数，类函数，静态函数之前的区别和练习
'''

class Person(object):
    __slots__ = ("name","age")

    #? 类属性
    num = 0 
    #? 构造函数
    def __init__(self,name,age) -> None:
        self.name = name 
        self.age = age
    #? 实例函数 -- self 表示当前对象
    def show(self):
        print("showing",self)
    #? 类函数 -- cls 表示当前类
    @classmethod
    def check(cls): 
        print("checking",cls)
    #? 静态函数
    @staticmethod
    def func():
        print("func -- ")

p1 = Person("UZI",25)
print(p1)
p1.show()
p1.check()
p1.func()
print(type(p1))
'''
<__main__.Person object at 0x0000026F92F13F40>
showing <__main__.Person object at 0x0000026F92F13F40>
checking <class '__main__.Person'>
func -- 
<class '__main__.Person'>
'''
'''
不同点：
    1> 实例函数不需要装饰器，类函数和静态函数需要
    2> 形参不同，实例函数 -- self ， 类函数 -- cls ， 静态函数没有
    3> 调用不同 类函数和静态函数都可以通过对象，类调用，但是实例函数只能通过对象调用
    4> 使用场景不同，如果要封装一个工具类，对参数没有要求，则尽量使用静态函数
相同点：
    1> 可以使用默认参数，关键字参数，不定长参数
    2> 可以有返回值
'''

class Person(object):
    __slots__ = ("name","age")

    #? 类属性
    num = 0 
    #? 构造函数
    def __init__(self,name,age) -> None:
        self.name = name 
        self.age = age
    #? 实例函数 -- self 表示当前对象
    def show(self):
        print("showing")
        #! 在类中，实力函数之间相互调用，必须通过self调用
        self.draw()
    def draw(self):
        print("drawing")

    #? 类函数 -- cls 表示当前类
    @classmethod
    def check(cls): 
        print("checking")

        #! 在类函数中调用实例函数，可以通过cls 创建一个对象，然后再调用
        p = cls("MING",25)
        p.show()

    #? 静态函数
    @staticmethod
    def func():
        print("func -- ")

p1 = Person("UZI",25)
p1.show()
p1.check()

#? 练习 -- 定义一个工具类，可以实现两个数加减乘除的运算
class Math(object):
    @staticmethod
    def add(num1,num2):
        return num1+num2
    
    @staticmethod
    def sub(num1,num2):
        return  num1 -  num2
    
    @staticmethod
    def mul(num1,num2):
        return num1*num2

    @staticmethod
    def div(num1,num2):
        return  num1/num2

print(Math.add(2,3))
print(Math.sub(2,3))
print(Math.mul(2,3))
print(Math.div(2,3))

'''
5
-1
6
0.6666666666666666
'''


#TODO 多态
'''
一种事物[一个对象]的多种体现形式
定义时并不确定是什么类型，要调用的是哪个方法，只有运行的时候才可以确定调用的是哪个？
注意：继承是多态的前提
'''
#& 一个对象的多种体现形式
class Animal(object):
    pass
class WalkAnimal(Animal):
    pass
class Cat(WalkAnimal):
    pass

cat = Cat()
#? isinstance(对象,类) 判断一个对象是否是指定类的对象，如果是，则返回True
print(isinstance(cat,Cat))
print(isinstance(cat,WalkAnimal))
print(isinstance(cat,Animal))
print(isinstance(cat,object))
'''
True
True
True
True
'''
a = Animal()
print(isinstance(a,Animal))
print(isinstance(a,Cat))
'''
True
False
'''

#& 定义时并不确定是什么类型，要调用的是哪个方法，只有运行的时候才可以确定调用的是哪个？
class Animal():
    def eat(self):
        print(type(self),"eating")
class Dog(Animal):
    pass
class Cat(Animal):
    pass
class Bird(Animal):
    def eat(self):
        print("Bird--eating")
class Pig(Animal):
    def eat(self):
        print("Pig--eating")
class Fish(Animal):
    def eat(self):
        print("Fish--eating")

class Person():
    #? 存在问题：动物太多，使用的方法是一样的
    # def feed_cat(self,cat):
    #     cat.eat()
    # def feed_cat(self,dog):
    #     dog.eat()

    #? 现在不确定是哪个对象 -- ani 就是多态的体现
    def feed_animal(self,ani):
        ani.eat()
per = Person()
cat = Cat()
per.feed_animal(cat) #>> <class '__main__.Cat'> eating

#TODO 设计模式
'''
什么是设计模式？
    设计模式就是经过总结，优化，对我们经常会碰到的一些编程问题的可重用的解决方案
    设计模式更为高级，它是一种必须在特定情况下实现的一种方法模板。设计模式不会绑定具体的编程语言
    23种设计模式，其中比较常用的是单例设计模式，工厂设计模式，代理模式，装饰者模式
'''
'''
什么是单例设计模式？
    对象：可以称为实例
    单例：单个实例[单个对象],一个类只能创建一个对象
    程序运行过程中，确保某一个类只有一个实例，不管在哪个模块获取这个类的对象，获取到的都是一个对象。该类有一个静态方法，向整个工程提供这个实例。
    单例设计模式的核心：一个类有且仅有一个实例，并且这个实例需要应用于整个程序中，该类被称为单例类
'''
'''
应用场景：

'''

#& 变量在内存中的地址
a = 10
b = a
print(a is b)
print(id(a)== id(b))
'''
True
True
'''

#& 实现单例设计模式
#* 普通类 可以创建无数个对象
class Person():
    def __init__(self,name,age) -> None:
        self.name = name
        self.age = age
p1 = Person("UZI",25)
p2 = Person("MING",25)
print(p1)
print(p2)
print(p1 is p2)
print(id(p1) == id(p2))
'''
<__main__.Person object at 0x000002A44EEC3D60>
<__main__.Person object at 0x000002A44EEC3C10>
False
False
'''

#& 单例类方式一：从创建对象的根本上控制
#* 思路：从创建对象的根本上控制,因为真正创建对象的语句是super().__new__(cls)
#* 所以只需要控制该语句执行一次，就可以只创建一个对象
class Person():
    #? 定义一个类属性，用于表示当前类可以创建的唯一的对象/实例
    #? 定义类属性的原因：不同的对象访问到的类属性都是同一个
    #? 定义成私有的，为了防止类属性在类的外面通过类名被修改，则将该类属性定义为私有属性
    __instance = None

    def __new__(cls,*args,**kwargs):
        #? print("newing")
        #& 思路 判断私有属性 __instance 是否为None，如果为None，则键个该类创建的唯一的对象赋值给它，并返回。如果不为None，则说明已经创建过对象，则直接返回
        if not cls.__instance:
            #? 这个地方只会在第一次创建对象的时候进来，执行一次
            cls.__instance = super().__new__(cls)
        return cls.__instance

    def __init__(self,name,age) -> None:
        self.name = name
        self.age = age

p1 = Person("UZI",25) #? 创建对象
p2 = Person("MING",25) #? 获取对象
print(p1)
print(p2)
print(p1 is p2)
print(id(p1) == id(p2))
'''
<__main__.Person object at 0x000002B2AE243F10>
<__main__.Person object at 0x000002B2AE243F10>
True
True
'''
print(p1.name)
print(p2.name)
'''
MING
MING
'''

#& 装饰器不但可以装饰函数，还可以装饰类
#? 装饰器装饰函数
def wrapper(func):  #? func表示被装饰的函数
    def inner():
        func() #? 调用被装饰的原函数
        print("new -- ")
    return inner
@wrapper #? 调用wrapper()
def check():
    print("checking-- ")
check() #? 调用inner()
'''
checking-- 
new -- 
'''

#? 装饰器装饰类
def wrapper(cls):  #? cls表示被装饰的类
    def inner(*args,**kwargs): #? 打包
        return cls(*args,**kwargs) #? 创建被装饰的类的对象,调用被装饰的类__init__，拆包
    return inner

@wrapper
class Check():
    def __init__(self,name) -> None:
        self.name = name
t = Check("UZI") #? 调用inner()
print(t)
s = Check("MING")
print(s)


#? 单例模式的实现二
def singleton(cls):
    #? 定义一个函数作用域的变量，用于表示被装饰的类可以创建唯一的对象
    instance = None
    def getinstance(*args,**kwargs):
        nonlocal instance
        if not instance:
            instance =  cls(*args,**kwargs)
        return instance
    return getinstance

@singleton
class Person(): 
    def __init__(self,name,age) -> None:
        self.name = name
        self.age = age
p1 = Person("UZI",25) #? 调用inner()
p2 = Person("MING",25)
print(p1)
print(p2)
print(p1 is p2)
print(id(p1) == id(p2))
print(p1.name)
print(p2.name)

'''
<__main__.Person object at 0x0000024FA6A73F70>
<__main__.Person object at 0x0000024FA6A73F70>
True
True
UZI
UZI
'''

#&单例模式实现方式三
def singleton(cls):
    instance_dict = {}
    def getinstance(*args,**kwargs):
        if not instance_dict:
            instance_dict[cls] = cls(*args,**kwargs) #? 添加键值对
        return  instance_dict[cls] #?返回对象
    return getinstance
@singleton
class Person(): 
    def __init__(self,name,age) -> None:
        self.name = name
        self.age = age
p1 = Person("UZI",25) #? 调用inner()
p2 = Person("MING",25)
print(p1)
print(p2)
print(p1 is p2)
print(id(p1) == id(p2))
print(p1.name)
print(p2.name)

#TODO 异常
#? NameError:使用了一个未定义的变量
#? ValueError:传入一个不期望的值，即使类型是正确
#? TypeError:传入的数据类型不匹配
#? IndexError:当列表，元组或者字符串的索引越界
#? AttributeError:当一个对象访问了不存在的属性或者函数
#? UnboundLocalError:修改了其他作用域的变量
#? FileNotFoundError:当读取文件的时候，文件不存在
#? MoudleNotFoundError:模块导入错误，主要模块的路径有问题
#? ImportError:
#? SyntsaxError:Python代码非法，基本语法错误

#? 异常处理方式
#& try-except-else [常用]
'''
try:
    #? 可能存在异常的代码
except 错误码 as 变量:
    #? 出现异常之后的提示
'''
try:
    index = int(input())
    list1 = [23,5,7,8]
    num = list1[index]
    print(f"列表中的第{index}个元素为{num}")
#? 常用e，异常：Exception 
#? e指向的是出现异常的对象，该异常对象的类型是ValueError
#? 打印一个对象，默认输出该对象的地址，但是，打印异常对象的时候，输出的是异常的描述信息
#? 则说明异常类中已经重写了 __str__
except ValueError as e:
    print(e)
print("end")

#? 如果有多个except，每次只会执行其中一个except代码块
#? 执行顺序。如果try中的代码出现异常,从上往下依次遍历except分支，找到和出现的异常相匹配的except

try:
    index = int(input())
    list1 = [23,5,7,8]
    num = list1[index]
    print(f"列表中的第{index}个元素为{num}")
except IndexError as e:
    print("IndexError",e)
except ValueError as e:
    print("ValueError",e)
print("end")

#? 异常的父类为Exception，Exception的父类为BaseException,可以捕获任意异常
#? 如果不确定异常的具体类型，写异常的父类就好
try:
    index = int(input())
    list1 = [23,5,7,8]
    num = list1[index]
    print(f"列表中的第{index}个元素为{num}")
except Exception as e:
    print("Exception",e)
print("end")


#? 如果不指名异常的类型，只是标记出现了异常，但是无法查看异常的具体描述信息
try:
    index = int(input())
    list1 = [23,5,7,8]
    num = list1[index]
    print(f"列表中的第{index}个元素为{num}")
except:
    print("出现了异常")
print("end")

#? 也可以指定异常的范围
try:
    index = int(input())
    list1 = [23,5,7,8]
    num = list1[index]
    print(f"列表中的第{index}个元素为{num}")
except(ValueError,TypeError,NameError,ImportError):
    print("出现了异常")
print("end")

#? try - except - else 
#? 只有当try中的代码没有异常的时候,else才会被执行

try:
    f = open(r"git-demo\PythonCode\hello.txt","r",encoding="utf-8")
except FileNotFoundError as e:
    print("文件不存在",e)
except ValueError as e:
    print("打开模式错误",e)
except LookupError as e:
    print("编码格式错误",e)
else:
    print(f.read())

#? try-except-finally
#? 无论try中代码是否异常，finally分支都会被执行
#? 所以finally常用于程序执行完毕的清理行为，如：关闭文件，关闭数据库
try:
    f = open(r"git-demo\PythonCode\hello.txt","r",encoding="utf-8")
    print(f.read())
except FileNotFoundError as e:
    print("文件不存在",e)
except ValueError as e:
    print("打开模式错误",e)
except LookupError as e:
    print("编码格式错误",e)
finally:
    #? 关闭文件
    f.close()
    print("文件关闭成功") 

#? [面试题目]
#? 在try或者except中出现了return finally仍然会被执行
def func():
    try:
        index = int(input())
        list1 = [23,5,7,8]
        num = list1[index]
        print(f"列表中的第{index}个元素为{num}")
        return
    except(ValueError,TypeError,NameError,ImportError):
        print("出现了异常")
        return 
    finally:
        print("finally被执行了")
func()

#? raise语句 -- 抛出异常
'''
异常对象出现的具体表现形式：
A.根据具体的问题出现，将会产生一个异常对象
B.根据异常的类创建一个异常对象
'''
#* 可能性
try:
    index = int(input())
except ValueError as e:
    print(e)

#* 一定
#? raise 一般应用于自定义异常中
try:
    e = ValueError() #? 创建一个异常对象
    raise e #& 抛出异常对象
except ValueError as e:
    print(e)


#? assert -- 断言、预言
def div(a,b):
    return a/b
#?print(div(10,0)) 
#>>  ZeroDivisionError: division by zero

try :
    r = div(10,0)
    print(r)
except Exception as e:
    print(e)
#>> division by zero

#? assert 使用 在一定场景下可以代替if语句
def div(a,b):
    assert b!= 0,"b参数不能为0"
    #? 工作原理：
    '''
    如果预测成功，则代码正常运行，如果预测失败，则会抛出一个新的断言异常
    '''
    return a/b
r = div(10,0)
print(r)
#>> AssertionError: b参数不能为0

#? 自定义异常
#? [面试题] 自定义异常类的实现步骤
#? 自定义一个类，继承于Exception/BaseException
class MyException(Exception):
    def __init__(self,msg) -> None:
        super().__init__()
        self.msg = msg
    
    #? 重写 __str__ 返回异常的描述信息
    def __str__(self) -> str:
        return self.msg

    #? 定义一个实例函数，用于处理出现的异常
    def handle(self):
        print("处理异常")

try:
    raise MyException("出现了异常")
except MyException as e:
    print(e)
    e.handle()
'''
出现了异常
处理异常
'''

#? 具体案例
#? 自定义异常，表示上班迟到的异常
class LateException(Exception):
    __slots__ =("message",)
    def __init__(self,message) -> None:
        super().__init__()
        self.message = message
    def __str__(self) -> str:
        return self.message
    def handle(self):
        print("延迟下班时间")
time = float(input())
if time>8:
    try:
        raise LateException("不想上班！！！")
    except LateException as e:
        print("迟到的原因:",e)
        e.handle()
else:
    print("打工人打工魂")